package com.ejiaoyi.api.aop;

import cn.hutool.core.util.EnumUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.ejiaoyi.api.dto.Rest;
import com.ejiaoyi.api.enums.ApiCode;
import com.ejiaoyi.api.util.ApiUtil;
import com.ejiaoyi.common.annotation.ApiEnumValidator;
import com.ejiaoyi.common.annotation.ApiParamValidator;
import com.ejiaoyi.common.constant.Constants;
import com.ejiaoyi.common.enums.BaseEnum;
import com.ejiaoyi.common.exception.CustomException;
import com.ejiaoyi.common.util.CommonUtil;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;

/**
 * ApiParamValidator 注解AOP
 *
 * @author unknownChivalrous
 * @since 2020-5-13
 */
@Slf4j
@Aspect
@Order(2)
@Component
public class ApiParamValidatorAspect {

    /**
     * 用于SpEL表达式解析.
     */
    private final SpelExpressionParser parser = new SpelExpressionParser();

    @Pointcut("@annotation(com.ejiaoyi.common.annotation.EnabledApiParamValidator))")
    public void apiParamValidator() {

    }

    @AfterThrowing(pointcut = "apiParamValidator()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        log.error("EXCEPTION: " + e.getMessage());
    }

    @Around("apiParamValidator()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object[] args = proceedingJoinPoint.getArgs();
        Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
        Class<?> responseClass = method.getReturnType();

        try {
            this.apiValid(args);
        } catch (CustomException e) {
            if (responseClass == Rest.class) {
                return ApiUtil.convertRest(e.getMessage());
            } else {
                return ApiUtil.convertRestObject(e.getMessage());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return proceedingJoinPoint.proceed();
    }

    /**
     * 对参数列表检测ApiValid 注解内容进行数据验证
     *
     * @param args 参数列表
     * @throws CustomException 自定义异常
     */
    private void apiValid(Object[] args) throws CustomException, IllegalAccessException {
        Class<?> clazz;
        Field[] declaredFields;
        String condition;
        int length;
        ApiEnumValidator apiEnumValidatorAnn;
        Class<? extends Enum<?>> validEnumType;
        String validEnumParam;
        List<Object> enumList;
        Expression expression;
        EvaluationContext context;
        boolean conditionFlag;
        Object value;
        JSONField jsonFieldAnn;
        JsonProperty jsonPropertyAnn;
        String jsonNodeName = "";

        for (Object arg : args) {
            clazz = arg.getClass();
            declaredFields = clazz.getDeclaredFields();
            context = new StandardEvaluationContext();
            for (Field declaredField : declaredFields) {
                declaredField.setAccessible(true);
                context.setVariable(declaredField.getName(), declaredField.get(arg));
            }

            for (Field declaredField : declaredFields) {
                declaredField.setAccessible(true);
                value = declaredField.get(arg);

                // 如果参数是List 执行递归
                if (value instanceof List) {
                    List list = (List) value;
                    for (Object o : list) {
                        this.apiValid(o.getClass().getDeclaredFields());
                    }
                }

                // 检测属性是否包含@ApiValid注解
                ApiParamValidator apiParamValidatorAnn = declaredField.getAnnotation(ApiParamValidator.class);
                if (apiParamValidatorAnn == null) {
                    continue;
                }

                jsonFieldAnn = declaredField.getAnnotation(JSONField.class);
                if (jsonFieldAnn != null) {
                    jsonNodeName = jsonFieldAnn.name();
                } else {
                    jsonPropertyAnn = declaredField.getAnnotation(JsonProperty.class);
                    if (jsonPropertyAnn != null) {
                        jsonNodeName = jsonPropertyAnn.value();
                    }
                }

                condition = apiParamValidatorAnn.condition();
                if (StringUtils.isNotEmpty(condition)) {
                    expression = parser.parseExpression(condition);
                    conditionFlag = StringUtils.equalsIgnoreCase(Constants.TRUE, Objects.requireNonNull(expression.getValue(context)).toString());
                    if (!conditionFlag) {
                        continue;
                    }
                }

                if (CommonUtil.isEmpty(value)) {
                    Rest rest = new Rest();
                    rest.setCode(ApiCode.DATA_REQUIRED);
                    rest.setMsg(jsonNodeName + " can not be empty!");
                    throw new CustomException(JSONObject.toJSONString(rest));
                }

                length = apiParamValidatorAnn.length();
                if (length != 0) {
                    if (value instanceof String) {
                        if (String.valueOf(value).length() != length) {
                            Rest rest = new Rest();
                            rest.setCode(ApiCode.DATA_LENGTH);
                            rest.setMsg("the " + jsonNodeName + " is not support : " + value + ", the length should be " + length);
                            throw new CustomException(JSONObject.toJSONString(rest));
                        }
                    }
                }

                apiEnumValidatorAnn = apiParamValidatorAnn.validEnum();
                validEnumType = apiEnumValidatorAnn.type();
                if (validEnumType == BaseEnum.class) {
                    continue;
                }
                validEnumParam = StringUtils.isNotEmpty(apiEnumValidatorAnn.param()) ? apiEnumValidatorAnn.param() : "code";
                enumList = EnumUtil.getFieldValues(validEnumType, validEnumParam);
                if (!enumList.contains(value)) {
                    Rest rest = new Rest();
                    rest.setCode(ApiCode.DATA_MISMATCH);
                    rest.setMsg("the " + jsonNodeName + " is not support : " + value);
                    throw new CustomException(JSONObject.toJSONString(rest));
                }
            }
        }
    }
}
